该 SIMT(单指令,多线程) 模型是 GPU 架构的核心。虽然你编写的是单个线程的代码,但硬件会将它们组织成两级层次结构—— 网格 和 块。为最大化效率,硬件还会将这些块进一步划分为 32 个线程的单元,称为 线程束。
1. SIMT 与 SIMD 的区别
与 CPU 的 SIMD(如 SSE/AVX)不同,后者需要手动将数据打包到寄存器中;而 SIMT 允许线程在逻辑上表现为独立执行。硬件会自动将线程分组为线程束,并为全部 32 个线程统一获取一条指令,在锁步状态下执行。
2. 线性化规则
程序员使用 threadIdx.x, y, z 来编写逻辑,但硬件会将其扁平化为一维序列以供调度:
索引 = x + (y × blockDim.x) + (z × blockDim.x × blockDim.y)
由于 x 维度 是变化最快的索引,因此具有连续 threadIdx.x 值的线程通常会被分配到同一个线程束中,这对 内存合并。
main.py
TERMINALbash — 80x24
> Ready. Click "Run" to execute.
>
QUESTION 1
What is the physical scheduling 'atom' (minimum unit) in NVIDIA's SIMT model?
A single Thread
A Warp (32 threads)
A Streaming Multiprocessor
A Grid
✅ Correct!
The hardware fetches one instruction for a warp of 32 threads. It is the minimum scheduling unit.❌ Incorrect
While you write code for single threads, the hardware dispatches them in groups of 32 (Warps).QUESTION 2
In an 8x8 thread block, which threads will be assigned to Warp 0?
Threads with linear IDs 0 through 31
Threads with y=0 and y=1
Only threads where x=0
All 64 threads belong to Warp 0
✅ Correct!
Hardware linearizes the block (row-major based on x) and takes the first 32 threads for Warp 0.❌ Incorrect
Warp partitioning is based on the linearized 1D index, regardless of dimensions.QUESTION 3
How does SIMT differ from traditional SIMD (like SSE/AVX)?
SIMT requires manual packing of vector registers.
SIMT allows threads to operate independently at the software level while hardware manages vectorization.
SIMD is only for GPUs; SIMT is for CPUs.
There is no difference; they are synonymous.
✅ Correct!
SIMT abstracts the hardware vectorization, allowing for more flexible control flow and easier programming.❌ Incorrect
Manual packing is a hallmark of SIMD. SIMT handles grouping via hardware warps.QUESTION 4
Using the linearization formula, what is the ID of T(2, 1, 0) in a block with blockDim.x=16 and blockDim.y=16?
3
18
32
17
✅ Correct!
Index = 2 + (1 * 16) + (0 * 16 * 16) = 18.❌ Incorrect
Follow the formula: x + (y * blockDim.x) + (z * blockDim.x * blockDim.y).QUESTION 5
What happens if threads within a warp take different execution paths (e.g., an if-else statement)?
The warp executes all paths in parallel without penalty.
The warp splits into two warps.
The hardware serializes the paths, disabling threads not on the current path.
The kernel crashes.
✅ Correct!
This is control flow divergence; it reduces efficiency because the paths are executed sequentially.❌ Incorrect
Hardware cannot split a warp; it must execute divergent paths one after another.Architectural Analysis: Linearization and Bandwidth
Applying Warp Partitioning to Matrix Addition
You are optimizing a matrix addition kernel on a 2D grid. The threads are organized into 8x8 blocks. You are considering using shared memory to improve performance.
Q
1. [Reading Context: Figure 6.1 shows an example of placing threads of a two-dimensional (2D) block into linear order.] Draw out/Represent the partitioning of an 8x8 thread block into warps. Which thread index (x,y) marks the end of Warp 0?
Solution:
In an 8x8 block (64 threads), threads are linearized using $Index = tx + (ty \times 8)$. Warp 0 covers Linear IDs 0 to 31. T(0,0) is ID 0. ID 31 is calculated as $tx=7, ty=3$ ($7 + 3 \times 8 = 31$). Therefore, Warp 0 starts at $T(0,0)$ and ends with $T(7,3)$. Warp 1 begins at $T(0,4)$ (ID 32) and ends at $T(7,7)$ (ID 63).
In an 8x8 block (64 threads), threads are linearized using $Index = tx + (ty \times 8)$. Warp 0 covers Linear IDs 0 to 31. T(0,0) is ID 0. ID 31 is calculated as $tx=7, ty=3$ ($7 + 3 \times 8 = 31$). Therefore, Warp 0 starts at $T(0,0)$ and ends with $T(7,3)$. Warp 1 begins at $T(0,4)$ (ID 32) and ends at $T(7,7)$ (ID 63).
Q
2. Consider the matrix addition where each element of the output matrix is the sum of the corresponding elements of the two input matrices. Can one use shared memory to reduce the global memory bandwidth consumption? Explain with details (approx. 150 words).
Solution:
No, shared memory cannot reduce global memory bandwidth for matrix addition. In matrix addition, each output element $C[i][j]$ is the sum of $A[i][j] + B[i][j]$. Each element from the input matrices $A$ and $B$ is accessed exactly once by exactly one thread to compute its unique output. Shared memory is a high-speed scratchpad designed for data reuse—where multiple threads within a block read the same global memory address repeatedly (as seen in matrix multiplication). Since there is zero data commonality between threads in matrix addition, loading data into shared memory first would actually increase overhead. It would require one global load to shared memory, a `__syncthreads()` barrier, and then a shared memory load before the addition. This adds instruction count and synchronization latency without decreasing the number of global memory transactions. To optimize matrix addition, one should focus on global memory coalescing rather than shared memory utilization.
No, shared memory cannot reduce global memory bandwidth for matrix addition. In matrix addition, each output element $C[i][j]$ is the sum of $A[i][j] + B[i][j]$. Each element from the input matrices $A$ and $B$ is accessed exactly once by exactly one thread to compute its unique output. Shared memory is a high-speed scratchpad designed for data reuse—where multiple threads within a block read the same global memory address repeatedly (as seen in matrix multiplication). Since there is zero data commonality between threads in matrix addition, loading data into shared memory first would actually increase overhead. It would require one global load to shared memory, a `__syncthreads()` barrier, and then a shared memory load before the addition. This adds instruction count and synchronization latency without decreasing the number of global memory transactions. To optimize matrix addition, one should focus on global memory coalescing rather than shared memory utilization.